package com.spukmk3me.impl.libgdx;

/* $if SPUKMK3ME_DEBUG$ */
import com.spukmk3me.debug.Logger;
/* $endif$ */
import com.spukmk3me.math.FPMath;
import com.spukmk3me.math.Matrix4f;
import com.spukmk3me.math.Rectangle;
import com.spukmk3me.math.SimpleMatrix4f;
import com.spukmk3me.math.Vector3f;
import com.spukmk3me.scene.Entity;
import com.spukmk3me.video.Font;
import com.spukmk3me.video.Image;
import com.spukmk3me.video.ImageRegion;
import com.spukmk3me.video.Polygon;
import com.spukmk3me.video.Sprite;
import com.spukmk3me.video.Text;
import com.spukmk3me.video.TextSetting;
import com.spukmk3me.video.VideoAPI;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public final class LibgdxVideoAPI extends VideoAPI
{
    public LibgdxVideoAPI( SpriteBatch batch )
    {
        m_initialized   = false;
        m_renderBatch   = new LibgdxRenderBatch( batch );
    }
    
    @Override
    public void init()
    {
        if ( m_initialized )
            return;
        
        /* $if SPUKMK3ME_DEBUG$ */
        Logger.log( "Initializing Libgdx video API..." );
        /* $endif$ */

        m_initialized = true;
        
        // Image rendering initialization
        //m_cam = new OrthographicCamera();
        //resize( Gdx.graphics.getWidth(), Gdx.graphics.getHeight() );
        
        m_clipData = new Rectangle();
        m_lastClipData = new Rectangle();
        
        m_additionalColor       = new float[ 4 ];
        m_mvMatrixStack         = new Matrix4f[ 16 ];
        m_mvScaleStack          = new Vector3f[ 16 ];
        m_mvTransStack          = new Vector3f[ 16 ];
        m_mvNeedUpdateGLMatrix  = new boolean[ 16 ];
        m_mvMatrixStackIndex    = 0;
        
        for ( int i = 0; i != m_mvMatrixStack.length; ++i )
        {
            m_mvMatrixStack[ i ] = new SimpleMatrix4f();
            m_mvScaleStack[ i ] = new Vector3f();
            m_mvTransStack[ i ] = new Vector3f();
        }
        
        ///////////////////////////////////////////
        m_useBlending = false;
        markGLCreateContext();
        
        /* $if SPUKMK3ME_DEBUG$ */
        Logger.log( "done.\n" );
        /* $endif$ */
    }
    
    @Override
    public void cleanup()
    {
        //m_batch.end();
    }
    
    @Override
    public void startRendering( boolean clearScreen, int clearColor, long deltaMilliseconds )
    {
        /*Gdx.gl10.glDepthMask( false );
        Gdx.gl10.glEnable( GL10.GL_TEXTURE_2D );
        
        Gdx.gl10.glEnableClientState( GL10.GL_VERTEX_ARRAY );
        Gdx.gl10.glEnableClientState( GL10.GL_COLOR_ARRAY );
        Gdx.gl10.glEnableClientState( GL10.GL_TEXTURE_COORD_ARRAY );
        
        if ( m_glCreateNewContext )
        {
            Pixmap whitePixmap = new Pixmap( 1, 1, Format.RGBA4444 );
            whitePixmap.setColor( Color.WHITE );
            whitePixmap.fillRectangle( 0, 0, 1, 1 );
            m_whiteTexture = new Texture( whitePixmap );
            
            m_glCreateNewContext = false;
        }
        
        if ( m_useBlending )
        {
            Gdx.gl10.glEnable( GL10.GL_BLEND );
            Gdx.gl10.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
        }
        else
            Gdx.gl10.glDisable( GL10.GL_BLEND );
            
        
        if ( clearScreen )
        {
            Gdx.gl10.glClearColor(
                ((clearColor >> 16) & 0x00FF) / 255f,
                ((clearColor >> 8) & 0x00FF) / 255f,
                (clearColor & 0x00FF) / 255f,
                ((clearColor >> 24) & 0x00FF) / 255f );
            Gdx.gl10.glClear( GL10.GL_COLOR_BUFFER_BIT );
        }
        
        Gdx.gl10.glMatrixMode( GL10.GL_PROJECTION );
        Gdx.gl10.glLoadMatrixf( m_cam.combined.val, 0 );
        Gdx.gl10.glMatrixMode( GL10.GL_MODELVIEW );
        Gdx.gl10.glLoadIdentity();*/
        
        m_deltaTimeL = deltaMilliseconds;
        m_deltaTimeI = FPMath.fpDiv( (int)m_deltaTimeL << 16, 0x03E80000 );

        m_mvMatrixStackIndex = 0;
        m_mvMatrixStack[ 0 ].reset();
        m_mvNeedUpdateGLMatrix[ 0 ] = false;
        m_mvScaleStack[ 0 ].set( 1f, 1f, 1f );
        m_mvTransStack[ 0 ].set( 0f, 0f, 0f );
    }
    
    public void finishRendering()
    {
    }

    public Object getProperty( String propertyName )
    {
        if ( propertyName.equals( PROPERTY_NAME ) )
            return LIBGDX_VIDEOAPI_NAME;
        
        if ( propertyName.equals( PROPERTY_VERSION ) )
            return LIBGDX_VIDEOAPI_VER;
        
        return null;
    }
    
    @Override
    public int getScreenHeight()
    {
        return Gdx.graphics.getHeight();
    }

    @Override
    public int getScreenWidth()
    {
        return Gdx.graphics.getWidth();
    }
    
    @Override
    public void setClip( int x, int y, int w, int h )
    {
        //System.out.println( "Clip input: " + x + ' ' + y + ' ' + w + ' ' + h );
        m_lastClipData.x = Math.min( getScreenWidth() - 1, Math.max( 0, x ) );
        m_lastClipData.y = Math.min( getScreenHeight() - 1, Math.max( 0, y ) );
        m_lastClipData.w = Math.min( getScreenWidth() - m_clipData.x, Math.max( 0, w ) );
        m_lastClipData.h = Math.min( getScreenHeight() - m_clipData.y, Math.max( 0, h ) );
        //System.out.println( "Clip: " + m_clipData.x + ' ' + m_clipData.y + ' ' + m_clipData.w + ' ' + m_clipData.h );
        
        if ( !m_lastClipData.equals( m_clipData ) )
        {
        	m_clipData.copy( m_lastClipData );
        	m_renderBatch.batch.flush();
	        
	        if (    (m_clipData.x == 0) && (m_clipData.y == 0) &&
	                (m_clipData.w == getScreenWidth()) && (m_clipData.h == getScreenHeight()) )
	            Gdx.gl.glDisable( GL10.GL_SCISSOR_TEST );
	        else
	        {
	            Gdx.gl.glEnable( GL10.GL_SCISSOR_TEST );
	            Gdx.gl.glScissor( m_clipData.x, m_clipData.y, m_clipData.w, m_clipData.h );
	        }
        }
    }
    
    @Override
    public long getClip()
    {
        return  ((m_clipData.x & 0x0000FFFF) << 48) |
                ((m_clipData.y & 0x0000FFFF) << 32) |
                ((m_clipData.w & 0x0000FFFF) << 16) |
                (m_clipData.h & 0x0000FFFF);
    }
    
    @Override
    public void getClip( Rectangle rect )
    {
        rect.copy( m_clipData );
    }
    
    @Override
    public void setPlaneColor( int color )
    {
        m_planeColor = Color.toFloatBits( (color >> 16) & 0x00FF,
            (color >> 8) & 0x00FF, color & 0x00FF, (color >> 24) & 0x00FF );
    }
    
    @Override
    public void setAdditionalColor( int color )
    {
        boolean useAdditionalColor = color != 0;
        
        if ( useAdditionalColor != m_useAdditionalColor )
        {
            if ( useAdditionalColor )
            {
                /*m_renderBuffer.flush();
                
                m_additionalColor[ 0 ] = (float)((color >> 16) & 0x00FF) / 255f;
                m_additionalColor[ 1 ] = (float)((color >> 8) & 0x00FF) / 255f;
                m_additionalColor[ 2 ] = (float)(color & 0x00FF) / 255f;
                m_additionalColor[ 3 ] = (float)((color >> 24) & 0x00FF) / 255f;
                
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL11.GL_COMBINE );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_ADD );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_ADD );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC0_RGB, GL11.GL_TEXTURE );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC0_ALPHA, GL11.GL_TEXTURE );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT );
                Gdx.gl10.glTexEnvfv( GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, m_additionalColor, 0 );*/
            }
            else
            {
                /*m_renderBuffer.flush();
                Gdx.gl10.glTexEnvf( GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE );
                m_additionalColor[ 0 ] = m_additionalColor[ 1 ] = m_additionalColor[ 2 ] = m_additionalColor[ 3 ] = 0;
                Gdx.gl10.glTexEnvfv( GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, m_additionalColor, 0 );*/
            }
        }
        
        m_useAdditionalColor = useAdditionalColor;
    }
    
    @Override
    public void setBlending( boolean useBlending )
    {
        if ( m_useBlending == useBlending )
            return;
        
        m_useBlending = useBlending;
        
        if ( useBlending )
        {
            m_renderBatch.batch.enableBlending();
            m_renderBatch.batch.setBlendFunction( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
        }
        else
            m_renderBatch.batch.disableBlending();
    }
    
    @Override
    public Matrix4f createMatrix()
    {
        //return new LibgdxMatrix4fWrapper();
        return new SimpleMatrix4f();
    }
    
    @Override
    public void getCurrentMatrix( Matrix4f m )
    {
        m.copy( m_mvMatrixStack[ m_mvMatrixStackIndex ] );
    }
    
    @Override
    public void setMatrix( Matrix4f trans )
    {
        m_mvMatrixStack[ m_mvMatrixStackIndex ].copy( trans );
        m_renderBatch.setModelViewMatrix( trans );
    }

    @Override
    public void mulMatrix( Matrix4f m )
    {
        ++m_mvMatrixStackIndex;
        
        Matrix4f t = m_mvMatrixStack[ m_mvMatrixStackIndex ];
        
        t.copy( m );
        t.mul( m_mvMatrixStack[ m_mvMatrixStackIndex - 1 ] );
        
        m_renderBatch.setModelViewMatrix( t );
    }

    @Override
    public void rollbackMatrix()
    {
        --m_mvMatrixStackIndex;
        m_renderBatch.setModelViewMatrix( m_mvMatrixStack[ m_mvMatrixStackIndex ] );
    }
    
    @Override
    public void render( Entity entity, boolean animating )
    {
        switch ( entity.getType() )
        {
            case Entity.ET_IMAGE:
                {
                    LibgdxImageRegion imgRegion = (LibgdxImageRegion)entity;
                    
                    m_renderBatch.drawImg( ((LibgdxImage)imgRegion.img),
                        imgRegion.w, imgRegion.h,
                        imgRegion.u1, imgRegion.v1, imgRegion.u2, imgRegion.v2, m_planeColor );
                }
                
                break;
                
            case Entity.ET_SPRITE:
                {
                    LibgdxSprite sprite = (LibgdxSprite)entity;
                    
                    int framePosition = sprite.getFramePosition( sprite.getFrameIndex() );
                    
                    if ( framePosition != -1 )
                    {
                        float u = sprite.u + (framePosition % sprite.nW) * sprite.stepU;
                        float v = sprite.v + (framePosition / sprite.nW) * sprite.stepV;
                        
                        m_renderBatch.drawImg( ((LibgdxImage)sprite.image),
                            sprite.spriteW, sprite.spriteH,
                            u, v + sprite.stepV, u + sprite.stepU, v, m_planeColor );
                    }
                    
                    if ( animating )
                        sprite.update( m_deltaTimeI );
                }
                
                break;
                
            case Entity.ET_POLYGON:
                {
                    Polygon poly = (Polygon)entity;
                    
                    m_renderBatch.drawBlankPolygon( m_whiteTexture, poly, m_planeColor );
                }
                
                break;
                
            case Entity.ET_TEXT:
                m_renderBatch.drawText( (Text)entity, m_planeColor );
                break;
                
            /* $if SPUKMK3ME_DEBUG$ */
            default:
                Logger.log( "Unknown type of entity: " + entity.getType() + '\n' );
            /* $endif$ */
                
        }
    }
    
    @Override
    public void renderImage( Image img, int srcX, int srcY, int srcW, int srcH )
    {
        m_renderBatch.drawImg( (LibgdxImage)img, srcW, srcH,
            (float)srcX / img.width(), (float)(srcY + srcH - 1) / img.height(),
            (float)(srcX + srcW - 1) / img.width(), (float)srcY / img.height(),
            m_planeColor );
    }
    
    @Override
    public void renderText( char[] s, Font font, int offset, int length, TextSetting setting )
    {
        m_renderBatch.drawText( s, font, 0, 0, offset, length, setting, m_planeColor );
    }
    
    @Override
    public void renderText( String s, Font font, int offset, int length, TextSetting setting )
    {
        m_renderBatch.drawText( s.toCharArray(), font, 0, 0, offset, length, setting, m_planeColor );
    }
    
    @Override
    public ImageRegion createImageRegion( Image img, int x, int y, int w, int h )
    {
        return new LibgdxImageRegion( img, x, y, w, h );
    }
    
    @Override
    public Sprite createSprite( Image image, int x, int y, int w, int h,
        int spriteW, int spriteH, int[] framePositions, int[] frameSteps )
    {
        return new LibgdxSprite( image, x, y, w, h, spriteW, spriteH, framePositions, frameSteps );
    }
    
    /*void resize( int width, int height )
    {
        m_cam.direction.set( 0f, 0f, -1f );
        m_cam.up.set( 0f, 1f, 0f );
        m_cam.viewportWidth = width;
        m_cam.viewportHeight = height;
        m_cam.position.x = (float)width / 2;
        m_cam.position.y = (float)height / 2;
        m_cam.update();
    }*/
    
    void markGLCreateContext()
    {
        m_glCreateNewContext = true;
    }
    
    private static final String LIBGDX_VIDEOAPI_NAME    = "libgdx";
    private static final String LIBGDX_VIDEOAPI_VER     = "0.1";
    
    // Drawing data
    //private OrthographicCamera  m_cam;
    private LibgdxRenderBatch   m_renderBatch;
    
    private Texture             m_whiteTexture;
    private float[]             m_additionalColor;
    private float               m_planeColor;
    private boolean             m_useAdditionalColor, m_useBlending;
    
    private Matrix4f[]          m_mvMatrixStack;
    private Vector3f[]          m_mvScaleStack, m_mvTransStack;
    private boolean[]           m_mvNeedUpdateGLMatrix;
    private int                 m_mvMatrixStackIndex;
    
    private Rectangle           m_clipData, m_lastClipData;
    
    private long                m_deltaTimeL;
    private int                 m_deltaTimeI;
    
    private boolean m_initialized, m_glCreateNewContext;    
}
